package pl.koziolekweb.pmt;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

/**
 * Klasa pozwala na generowanie pokrycia testami kodu, który nie musi być
 * testowany, ale mamy jego pliki źródłowe. przykladem takiego kodu są np.:
 * <ul>
 * <li>Klasy wygenerowane za pomocą wsimport z pliku opisu usługi</li>
 * <li>Automatycznie wygenerowane encje JPA</li>
 * </ul>
 * Celem tego typu testów jest zapewnienie maksymalnego pokrycia kodu w
 * narzędziach sprawdzających. Pozwala to uniknąć sytuacji w których generowany
 * automatycznie kod wpływa na statystyki pokrycia i tym samym na wyniki buidów
 * w narzędziach CI.
 * 
 * @author bartlomiejk
 * 
 */
public class Coverager {

	public static void test(String pkgname) throws ClassNotFoundException,
			InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {
		List<Class<?>> classes = getClassesForPackage(pkgname);
		for (Class<?> c : classes) {
			Method[] methods = c.getDeclaredMethods();
			Object o = c.newInstance();
			for (Method m : methods) {
				if (m.getName().equals("equals")) {
					testEquals(m, o, c);
					continue;
				}
				if (!(m.getParameterTypes().length > 0)) {
					try {
						m.invoke(o, (Object[]) null);
					} catch (Exception e) {

					}
				} else {
					Object[] args = new Object[m.getParameterTypes().length];
					Arrays.fill(args, null);
					try {
						m.invoke(o, args);
					} catch (Exception e) {
						try {
							int[] args2 = new int[m.getParameterTypes().length];
							Arrays.fill(args2, 0);
							m.invoke(o, args2);
						} catch (Exception e2) {
						}
					}
				}
			}
		}
	}

	private static void testEquals(Method m, Object o, Class<?> c) {
		o.equals(null); // equals null
		o.equals(o); // equals this
		o.equals(new Object()); // not equals, different type
		try {
			o.equals(PrivateMemberTester.runMethod("clone", o, (Object[]) null,
					o.getClass(), (Class<?>[])null));
			o.equals(o.getClass().newInstance()); // ? new instance of same
			// type. Probably not
			// equals.
		} catch (Throwable e) {
			e.printStackTrace();
		}

	}

	private static List<Class<?>> getClassesForPackage(String pckgname)
			throws ClassNotFoundException {
		// This will hold a list of directories matching the pckgname. There may
		// be more than one if a package is split over multiple jars/paths
		ArrayList<File> directories = new ArrayList<File>();
		try {
			ClassLoader cld = Thread.currentThread().getContextClassLoader();
			if (cld == null) {
				throw new ClassNotFoundException("Can't get class loader.");
			}
			String path = pckgname.replace('.', '/');
			// Ask for all resources for the path
			Enumeration<URL> resources = cld.getResources(path);
			while (resources.hasMoreElements()) {
				directories.add(new File(URLDecoder.decode(resources
						.nextElement().getPath(), "UTF-8")));
			}
		} catch (NullPointerException x) {
			throw new ClassNotFoundException(
					pckgname
							+ " does not appear to be a valid package (Null pointer exception)");
		} catch (UnsupportedEncodingException encex) {
			throw new ClassNotFoundException(
					pckgname
							+ " does not appear to be a valid package (Unsupported encoding)");
		} catch (IOException ioex) {
			throw new ClassNotFoundException(
					"IOException was thrown when trying to get all resources for "
							+ pckgname);
		}

		ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
		// For every directory identified capture all the .class files
		for (File directory : directories) {
			if (directory.exists()) {
				// Get the list of the files contained in the package
				String[] files = directory.list();
				for (String file : files) {
					// we are only interested in .class files
					if (file.endsWith(".class")) {
						// removes the .class extension
						if (file.endsWith("Test.class")) {
							continue;
						}
						classes.add(Class.forName(pckgname + '.'
								+ file.substring(0, file.length() - 6)));
					}
				}
			} else {
				throw new ClassNotFoundException(pckgname + " ("
						+ directory.getPath()
						+ ") does not appear to be a valid package");
			}
		}
		return classes;
	}
}
